package part8

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/sha256"
	"encoding/gob"
	"encoding/hex"
	"fmt"
	"log"
	"math/big"
	"strings"
)

//挖矿奖励
const subsidy = 10

//交易
type Transaction struct {
	ID   []byte     //交易hash值 = hash(vin + vout)
	Vin  []TXInput  //输入
	Vout []TXOutput //输出
}
type TXOutput struct {
	Value      int //多少值，就是多少比特币
	Pubkeyhash []byte
}

type TXInput struct {
	Txid      []byte //之前的交易hash值
	Vout      int    //是之前交易的第几个output
	Signature []byte
	Pubkey    []byte
}

//新建一个挖矿奖励交易
func NewCoinbaseTX(to, data string) Transaction {
	if data == "" {
		data = fmt.Sprintf("Reward to '%s'", to)
	}
	//Coinbas的input的hash为空，不需要
	txin := TXInput{[]byte{}, -1, nil, []byte(data)}
	//subsidy奖励
	txout := NewTXOutput(subsidy, to)
	//组成一个交易
	tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}}
	tx.ID = tx.Hash()

	return tx
}

//构造一个output
func NewTXOutput(value int, address string) TXOutput {
	txo := &TXOutput{value, nil}
	//把address中的公钥赋给output
	txo.Lock([]byte(address))
	return *txo
}

//从地址中提取公钥hash
func (out *TXOutput) Lock(address []byte) {
	pubKeyHash := AddressToPubHash(address)
	out.Pubkeyhash = pubKeyHash
}

//AddressToPubHash
func AddressToPubHash(address []byte) []byte {
	pubKeyHash := Base58Decode(address)
	pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-4]
	return pubKeyHash
}

//新建交易，发送币
func NewUTXOTransaction(wallet *Wallet, to string, amount int, bc *Blockchain) Transaction {
	var inputs []TXInput
	var outputs []TXOutput

	acc, allValidOutputIndex, unspentOutputMap := bc.FindSpendableOutputs(string(wallet.GetAddress()))

	if acc < amount {
		log.Panic("ERROR: Not enough funds")
	}

	//找到足够的output
	validOutputs := make(map[string][]int)
	accumulated := 0
	for txID, outIndexList := range allValidOutputIndex {
		for _, outIndex := range outIndexList {
			if accumulated < amount {
				out := unspentOutputMap[txID][outIndex]
				accumulated += out.Value
				validOutputs[txID] = append(validOutputs[txID], outIndex)
			}
		}
	}

	// 构建input，把找到的input的打个包inputs
	for txid, outs := range validOutputs {
		txID, _ := hex.DecodeString(txid)

		for _, out := range outs {
			input := TXInput{txID, out, nil, wallet.PublicKey}
			inputs = append(inputs, input)
		}
	}

	//构建output
	outputs = append(outputs, NewTXOutput(amount, to))
	if acc > amount {
		//自己的地址
		from := string(wallet.GetAddress())
		//如果需要找零，新建一个output
		outputs = append(outputs, NewTXOutput(acc-amount, from))
	}

	tx := Transaction{nil, inputs, outputs}
	tx.ID = tx.Hash()
	//签名
	bc.SignTransaction(&tx, wallet.Privatekey)
	return tx
}

//给交易签名
func (tx *Transaction) Sign(privKey ecdsa.PrivateKey, prevTXs map[string]Transaction) {
	if tx.IsCoinbase() {
		return
	}

	//将会被签署的是修剪后的交易副本，而不是一个完整交易：
	txcopy := tx.TrimmedCopy()

	for inID, vin := range txcopy.Vin {
		prevTx := prevTXs[hex.EncodeToString(vin.Txid)]
		txcopy.Vin[inID].Signature = nil
		//PubKey 被设置为所引用输出的 PubKeyHas
		txcopy.Vin[inID].Pubkey = prevTx.Vout[vin.Vout].Pubkeyhash
		txcopy.ID = txcopy.Hash()
		txcopy.Vin[inID].Pubkey = nil

		r, s, _ := ecdsa.Sign(rand.Reader, &privKey, txcopy.ID)
		signature := append(r.Bytes(), s.Bytes()...)

		tx.Vin[inID].Signature = signature
	}
}

//验证签名
func (tx *Transaction) Verify(prevTXs map[string]Transaction) bool {
	if tx.IsCoinbase() {
		return true
	}
	txCopy := tx.TrimmedCopy()
	curve := elliptic.P256()

	for inID, vin := range tx.Vin {
		prevTX := prevTXs[hex.EncodeToString(vin.Txid)]
		txCopy.Vin[inID].Signature = nil
		txCopy.Vin[inID].Pubkey = prevTX.Vout[vin.Vout].Pubkeyhash
		txCopy.ID = txCopy.Hash()
		txCopy.Vin[inID].Pubkey = nil

		r := big.Int{}
		s := big.Int{}
		sigLen := len(vin.Signature)
		r.SetBytes(vin.Signature[:(sigLen)/2])
		s.SetBytes(vin.Signature[(sigLen / 2):])

		x := big.Int{}
		y := big.Int{}
		keyLen := len(vin.Pubkey)
		x.SetBytes(vin.Pubkey[:(keyLen / 2)])
		y.SetBytes(vin.Pubkey[(keyLen / 2):])

		rawPubkey := ecdsa.PublicKey{Curve: curve, X: &x, Y: &y}
		if !ecdsa.Verify(&rawPubkey, txCopy.ID, &r, &s) {
			return false
		}
	}
	return true
}

//是否是coinbase交易
func (tx Transaction) IsCoinbase() bool {
	return len(tx.Vin) == 1 && len(tx.Vin[0].Txid) == 0 && tx.Vin[0].Vout == -1
}

//hash 交易
func (tx *Transaction) Hash() []byte {
	var hash [32]byte

	txCopy := *tx
	txCopy.ID = []byte{}
	hash = sha256.Sum256(txCopy.Serialize())

	return hash[:]
}

//序列化Transaction
func (tx Transaction) Serialize() []byte {
	var encoded bytes.Buffer

	enc := gob.NewEncoder(&encoded)
	err := enc.Encode(tx)
	if err != nil {
		log.Panic(err)
	}

	return encoded.Bytes()
}

//打印交易
func (tx Transaction) String() string {
	var lines []string

	lines = append(lines, fmt.Sprintf("----Transaction %x:", tx.ID))

	for i, input := range tx.Vin {

		lines = append(lines, fmt.Sprintf("     Input %d:", i))
		lines = append(lines, fmt.Sprintf("       TXID:      %x", input.Txid))
		lines = append(lines, fmt.Sprintf("       Out:       %d", input.Vout))
		lines = append(lines, fmt.Sprintf("       Signature: %x", input.Signature))
		lines = append(lines, fmt.Sprintf("       PubKey:    %x", input.Pubkey))
	}

	for i, output := range tx.Vout {
		lines = append(lines, fmt.Sprintf("     Output %d:", i))
		lines = append(lines, fmt.Sprintf("       Value:  %d", output.Value))
		lines = append(lines, fmt.Sprintf("       Script: %x", output.Pubkeyhash))
	}

	return strings.Join(lines, "\n")
}

func (tx *Transaction) TrimmedCopy() Transaction {
	var inputs []TXInput
	var outputs []TXOutput
	for _, vin := range tx.Vin {
		inputs = append(inputs, TXInput{vin.Txid, vin.Vout, nil, nil})
	}
	for _, vout := range tx.Vout {
		outputs = append(outputs, TXOutput{vout.Value, vout.Pubkeyhash})
	}
	txCopy := Transaction{tx.ID, inputs, outputs}
	return txCopy
}
